/**
 * Copyright (c) 2009 Teoti Graphix, LLC See:
 *     http://tkarchitecture.teotigraphix.org
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * 
 * @author Michael Schmalle mschmalle@teotigraphix.com
 */
package org.teotigraphix.dx.patterns.manager.toolkit
{

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.events.IEventDispatcher;

import mx.core.IFactory;
import mx.core.UIComponent;
import mx.events.FlexEvent;
import mx.events.PropertyChangeEvent;
import mx.states.State;

import org.teotigraphix.dx.core.ToolKitConstants;
import org.teotigraphix.dx.core.ToolKitGlobals;
import org.teotigraphix.dx.core.ToolKitHelper;
import org.teotigraphix.dx.core.ToolKitUtil;
import org.teotigraphix.dx.event.SkinEvent;
import org.teotigraphix.dx.event.ToolKitEvent;
import org.teotigraphix.dx.interfaces.IActivate;
import org.teotigraphix.dx.interfaces.IBehaviorLayer;
import org.teotigraphix.dx.interfaces.IBehaviorLayerAware;
import org.teotigraphix.dx.interfaces.IComponentBehaviorCore;
import org.teotigraphix.dx.interfaces.IComponentModelCore;
import org.teotigraphix.dx.interfaces.IComponentSkin;
import org.teotigraphix.dx.interfaces.IComponentSkinCore;
import org.teotigraphix.dx.interfaces.ICoreBehaviorAware;
import org.teotigraphix.dx.interfaces.ICoreModelAware;
import org.teotigraphix.dx.interfaces.ICoreSkinAware;
import org.teotigraphix.dx.interfaces.IModelLayer;
import org.teotigraphix.dx.interfaces.IModelLayerAware;
import org.teotigraphix.dx.interfaces.ISkinAwareBehavior;
import org.teotigraphix.dx.interfaces.ISkinLayer;
import org.teotigraphix.dx.interfaces.ISkinLayerAware;
import org.teotigraphix.dx.interfaces.IToolKitComponent;
import org.teotigraphix.dx.interfaces.IToolKitFactory;
import org.teotigraphix.dx.interfaces.IToolKitFactoryAware;
import org.teotigraphix.dx.interfaces.IToolKitManager;
import org.teotigraphix.dx.interfaces.IValidatingBehavior;
import org.teotigraphix.dx.patterns.component.ToolKitFactory;
import org.teotigraphix.dx.patterns.component.skin.SkinFactory;
import org.teotigraphix.dx.patterns.manager.StyleBindingManager;
import org.teotigraphix.dx.patterns.manager.meta.ClassMetaData;
import org.teotigraphix.dx.patterns.manager.meta.SkinStateInfo;

[Mixin]

/**
 * @private
 * 
 * The default <code>IToolKitManager</code> implementation for the toolkit.
 * 
 * @author Michael Schmalle
 * @copyright Teoti Graphix, LLC
 * @date 07-20-09
 */
public class ToolKitManagerImpl
	   implements IToolKitManager
{
	include '../../../core/Version.as'
	
	//--------------------------------------------------------------------------
	//
	//  Public Static Initialization
	//
	//--------------------------------------------------------------------------
	
	ToolKitUtil.registerSingleton(
		"org.teotigraphix.dx.interfaces::IToolKitManager", 
		ToolKitManagerImpl);
	
	//--------------------------------------------------------------------------
	//
	//  Public Mixin :: Methods - ISystemManager
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	public static function init(root:DisplayObject):void
	{
	}
	
	//--------------------------------------------------------------------------
	//
	//  Private Class :: Properties
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private static var invalidations:InvalidPropertyCollection = 
		new InvalidPropertyCollection();
	
	/**
	 * @private
	 */
	private static var instance:IToolKitManager = null;
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 * @private
	 */	
	public function ToolKitManagerImpl()
	{
	}
	
	//--------------------------------------------------------------------------
	//
	//  IToolKitManager API :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Call in the <code>IToolKitComponent</code> constructor.
	 * 
	 * @param client An <code>IToolKitComponent</code> to initialize.
	 */
	public function activate(client:IToolKitComponent):void
	{
		// for now this needs to be called in the constructor
		// or the change events won't be picked up and save here
		// IE the change events to an mxml property won't get queued to be validated
		addClient(client);		
		
		if (client is IActivate)
			IActivate(client).activate(IEventDispatcher(client));
		
		client.addEventListener(FlexEvent.PREINITIALIZE, client_preinitializeHandler);
		
		// Halo, for states, we need to create the states on the component
		// that are defined by the [SkinState] metadata
		
		// TODO (mschmalle) deactivate() needs to take these states out
		var data:ClassMetaData = ToolKitUtil.resolveClassMetaData(client);
		var states:Array = data.skinStates;
		if (states && states.length > 0)
		{
			var len:int = states.length;
			for (var i:int = 0; i < len; i++)
			{
				var state:State = new State();
				state.name = SkinStateInfo(states[i]).id;
				UIComponent(client).states.push(state);
			}
		}
	}
	
	/**
	 * Call in the <code>IToolKitComponent</code> <code>parentChanged()</code>
	 * method.
	 * 
	 * @param client An <code>IToolKitComponent</code> to deactivate.
	 */
	public function deactivate(client:IToolKitComponent):void
	{
		if (client is IActivate)
			IActivate(client).deactivate(IEventDispatcher(client));
	}
	
	/**
	 * 
	 */
	public function validateParentChanged(
		client:IToolKitComponent, parent:DisplayObjectContainer):void
	{
		if (parent)
		{
			client.initializeToolKit();
		}
		else
		{
			removeClient(client);
			client.stopToolKit();
			client.toolKitInitialized = false
		}
	}
	
	/**
	 * 
	 */
	public function validateInitialization(client:IToolKitComponent):void
	{
		if (client.toolKitInitialized)
			return;
		
		preinitializeClient(client);
		
		validateCores(client);	
		validatePropertyMap(client);	
	}
	
	/**
	 * 
	 */
	public function validateInitializationComplete(client:IToolKitComponent):void
	{
		initializeClient(client);
		startClient(client);
		createComponents(client);
				
//		validatePropertyMap(client);
		
		client.toolKitInitialized = true;
	}	
	
	/**
	 * 
	 */
	public function validateSkinAttachment(
		client:IToolKitComponent, skin:IEventDispatcher):void
	{
		if (client is ICoreBehaviorAware)
		{
			var aware:ISkinAwareBehavior = ICoreBehaviorAware(client).
				coreBehavior as ISkinAwareBehavior;
			if (aware)
				client.dispatchEvent(new SkinEvent(SkinEvent.SKIN_ADD, skin));
		}
	}
	
	/**
	 * 
	 */
	public function validateSkinDetachment(
		client:IToolKitComponent, skin:IEventDispatcher):void
	{
		if (client is ICoreBehaviorAware)
		{		
			var aware:ISkinAwareBehavior = ICoreBehaviorAware(client).
				coreBehavior as ISkinAwareBehavior;
			if (aware)
				client.dispatchEvent(new SkinEvent(SkinEvent.SKIN_REMOVE, skin));
		}
	}
	
	private function validateCores(client:IToolKitComponent):void
	{
		var o:InvalidProperty;
		
		o = invalidations.removeInvalidProperty(client, ToolKitConstants.CORE_BEHAVIOR);
		if (o)
		{
			ToolKitHelper.setCoreBehavior(
				o.component, 
				o.newValue as IComponentBehaviorCore, 
				o.oldValue  as IComponentBehaviorCore);
		}
		
		o = invalidations.removeInvalidProperty(client, ToolKitConstants.CORE_MODEL);
		if (o)
		{
			ToolKitHelper.setCoreModel(
				o.component, 
				o.newValue as IComponentModelCore, 
				o.oldValue as IComponentModelCore);
		}
		
		o = invalidations.removeInvalidProperty(client, ToolKitConstants.CORE_SKIN);
		if (o)
		{
			ToolKitHelper.setCoreSkin(
				o.component, 
				o.newValue as IComponentSkin, 
				o.oldValue as IComponentSkin);
		}
	}
	
	/**
	 * 
	 */
	public function validatePreCommitProperties(client:IToolKitComponent):void
	{
		// this has to do with the coreBehavior needing to be reset before the
		// skin is created in SkinnableComponent.commitProperties()
		if (!invalidations.hasItem(client))
			return;
		
		validateCores(client);
	}	
	
	/**
	 * 
	 */
	public function validateCommitProperties(client:IToolKitComponent):void
	{
		validatePropertyMap(client);
		
		// for now all the core does is validate the skin style bindings
		// on skin change
		if (client is ICoreBehaviorAware)
		{		
			var validate:IValidatingBehavior = ICoreBehaviorAware(client).
				coreBehavior as IValidatingBehavior;
			if (validate is IValidatingBehavior)
				validate.validateProperties();
		}
	}	
	
	/**
	 * 
	 */
	public function validateStyleChanged(
		client:IToolKitComponent, styleProp:String):void
	{
		if (styleProp == "skinClass")
		{
			// this means the framework set the skinFactory and we need to
			var factory:IFactory = client.getStyle("skinFactory");
			if (factory is SkinFactory)
			{
				client.clearStyle("skinFactory");
			}			
		}
		
		if (client is ICoreBehaviorAware)
		{
			var validate:IValidatingBehavior = ICoreBehaviorAware(client).
				coreBehavior as IValidatingBehavior;
			if (validate is IValidatingBehavior)
				validate.validateStyle(styleProp);
		}
	}
	
	/**
	 * 
	 */
	public function validateStylesInitialized(client:IToolKitComponent):void
	{
		StyleBindingManager.initialize(client);
	}
	
	/**
	 * 
	 */
	public function validateMeasure(
		client:IToolKitComponent):void
	{
		if (client is ICoreBehaviorAware)
		{
			var validate:IValidatingBehavior = ICoreBehaviorAware(client).
				coreBehavior as IValidatingBehavior;
			if (validate is IValidatingBehavior)
				validate.validateSize();
		}
	}
	
	/**
	 * 
	 */
	public function validateUpdateDisplayList(
		client:IToolKitComponent, 
		unscaledWidth:Number, unscaledHeight:Number):void
	{
		if (client is ICoreBehaviorAware)
		{		
			var validate:IValidatingBehavior = ICoreBehaviorAware(client).
				coreBehavior as IValidatingBehavior;
			if (validate is IValidatingBehavior)
				validate.validateDisplayList(unscaledWidth, unscaledHeight);
		}
	}
	
	/**
	 * 
	 */
	public function validateProperties(component:IToolKitComponent):void
	{
		if (ToolKitGlobals.VALIDATION_EVENTS_ENABLED)
			component.dispatchEvent(new ToolKitEvent(ToolKitEvent.VALIDATE_PROPERTIES_COMPLETE));
	}
	
	/**
	 * 
	 */
	public function validateSize(component:IToolKitComponent):void
	{
		if (ToolKitGlobals.VALIDATION_EVENTS_ENABLED)
			component.dispatchEvent(new ToolKitEvent(ToolKitEvent.VALIDATE_SIZE_COMPLETE));
	}
	
	/**
	 * 
	 */
	public function validateDisplayList(component:IToolKitComponent):void
	{
		if (ToolKitGlobals.VALIDATION_EVENTS_ENABLED)
			component.dispatchEvent(new ToolKitEvent(ToolKitEvent.VALIDATE_DISPLAY_LIST_COMPLETE));
	}	
	
	//--------------------------------------------------------------------------
	//
	//  Protected :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	protected function addClient(client:IToolKitComponent):void
	{
		for each(var name:String in ToolKitConstants.toolKitPropertyMap)
		{
			client.addEventListener(
				name + "Changed", 
				client_propertyChangeHandler);
		}
		
		// TODO this is a performance hit!
		// but needed if the properties are just marked;
		// [Bindable]
		// public var coreBehavior:ICoreComponentBehavior
		client.addEventListener(
			PropertyChangeEvent.PROPERTY_CHANGE,
			client_propertyChangeHandler);
		
		invalidations.addItem(client, {});
	}
	
	/**
	 * @private
	 */
	protected function removeClient(client:IToolKitComponent):void
	{
		for each(var name:String in ToolKitConstants.toolKitPropertyMap)
		{
			client.removeEventListener(
				name + "Changed", 
				client_propertyChangeHandler);
		}
		
		client.removeEventListener(
			PropertyChangeEvent.PROPERTY_CHANGE,
			client_propertyChangeHandler);		
		
		invalidations.removeItem(client);
	}
	
	/**
	 * called before createChildren() for skin.
	 */
	protected function preinitializeClient(client:IToolKitComponent):void
	{
		var factory:IToolKitFactory = ToolKitHelper.getToolKitFactory(client);
		var faware:IToolKitFactoryAware = client as IToolKitFactoryAware;
		if (client)
		{
			// this happens if the toolKitFactoryClass is set and found in the 
			// getToolKitFactory() method, this is the only time the framework
			// will set this property explicitly
			if (!faware.toolKitFactory && factory)
				faware.toolKitFactory = factory;
		}
		
		var defaultFactory:IToolKitFactory = new ToolKitFactory();
		
		var coreb:IComponentBehaviorCore;
		var corem:IComponentModelCore;
		var coresk:IComponentSkinCore;
		
		var behaviorLayer:IBehaviorLayer;
		var modelLayer:IModelLayer;
		var skinLayer:ISkinLayer;
		
		var coreSkinAware:ICoreSkinAware = client as ICoreSkinAware;
		var coreBehaviorAware:ICoreBehaviorAware = client as ICoreBehaviorAware;
		var coreModelAware:ICoreModelAware = client as ICoreModelAware;
		
		var behaviorLayerAware:IBehaviorLayerAware = client as IBehaviorLayerAware;
		var modelLayerAware:IModelLayerAware = client as IModelLayerAware;
		var skinLayerAware:ISkinLayerAware = client as ISkinLayerAware;
		
		if (coreBehaviorAware && !coreBehaviorAware.coreBehavior)
		{
			if (!factory)
			{
				coreb = (coreBehaviorAware.coreBehavior != null) ? 
					coreBehaviorAware.coreBehavior : defaultFactory.createCoreBehavior();
			}
			else
			{
				coreb = factory.createCoreBehavior();
				if (coreb == null && coreBehaviorAware.coreBehavior != null)
					coreb = coreBehaviorAware.coreBehavior;
				else if (coreb == null)
					coreb = defaultFactory.createCoreBehavior();
			}
			coreBehaviorAware.coreBehavior = coreb;
		}
		
		if (coreModelAware && !coreModelAware.coreModel)
		{
			if (!factory)
			{
				corem = (coreModelAware.coreModel != null) ? 
					coreModelAware.coreModel : defaultFactory.createCoreModel();
			}
			else
			{
				corem = factory.createCoreModel();
				if (corem == null && coreModelAware.coreModel != null)
					corem = coreModelAware.coreModel;
				else if (corem == null)
					corem = defaultFactory.createCoreModel();
			}
			coreModelAware.coreModel = corem;
		}		
		
		if (behaviorLayerAware && !behaviorLayerAware.behaviorLayer)
		{
			if (!factory)
			{
				behaviorLayer = (behaviorLayerAware.behaviorLayer != null) ? 
					behaviorLayerAware.behaviorLayer : defaultFactory.createBehaviorLayer();
			}
			else
			{
				behaviorLayer = factory.createBehaviorLayer();
				if (behaviorLayer == null && behaviorLayerAware.behaviorLayer != null)
					behaviorLayer = behaviorLayerAware.behaviorLayer;
				else if (behaviorLayer == null)
					behaviorLayer = defaultFactory.createBehaviorLayer();
			}
			behaviorLayerAware.behaviorLayer = behaviorLayer;
		}
		
		if (modelLayerAware && !modelLayerAware.modelLayer)
		{
			if (!factory)
			{
				modelLayer = (modelLayerAware.modelLayer != null) ? 
					modelLayerAware.modelLayer : defaultFactory.createModelLayer();
			}
			else
			{
				modelLayer = factory.createModelLayer();
				if (modelLayer == null && modelLayerAware.modelLayer != null)
					modelLayer = modelLayerAware.modelLayer;
				else if (modelLayer == null)
					modelLayer = defaultFactory.createModelLayer();
			}
			modelLayerAware.modelLayer = modelLayer;
		}
		
		if (skinLayerAware && !skinLayerAware.skinLayer)
		{
			if (!factory)
			{
				skinLayer = (skinLayerAware.skinLayer != null) ? 
					skinLayerAware.skinLayer : defaultFactory.createSkinLayer();
			}
			else
			{
				skinLayer = factory.createSkinLayer();
				if (skinLayer == null && skinLayerAware.skinLayer != null)
					skinLayer = skinLayerAware.skinLayer;
				else if (skinLayer == null)
					skinLayer = defaultFactory.createSkinLayer();
			}
			skinLayerAware.skinLayer = skinLayer;
		}		
		
		if (coreSkinAware && !coreSkinAware.coreSkin)
		{
			if (factory)
			{
				coresk = factory.createCoreSkin();
			}
			coreSkinAware.coreSkin = coresk;
		}		
	}	
	
	/**
	 * called after create children for parts.
	 */
	protected function initializeClient(client:IToolKitComponent):void
	{
		client.dispatchEvent(new ToolKitEvent(ToolKitEvent.TOOL_KIT_INITIALIZE_COMPLETE));	
	}
	
	/**
	 * 
	 */
	protected function createComponents(client:IToolKitComponent):void
	{
		var factory:IToolKitFactory = ToolKitHelper.getToolKitFactory(client);
		if (factory)
			factory.createComponents(client);
	}	
	
	/**
	 * 
	 */
	protected function startClient(client:IToolKitComponent):void
	{
		client.startToolKit();
	}	
	
	//--------------------------------------------------------------------------
	//
	//  Private :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * @private
	 */
	private function validatePropertyMap(client:IToolKitComponent):void
	{
		invalidations.validate(client);
	}
	
	/**
	 * @private
	 */
	private function client_propertyChangeHandler(event:PropertyChangeEvent):void
	{
		if (ToolKitConstants.isValidToolKitProperty(event.property.toString()))
		{
			invalidations.propertyChanged(event);
		}
		else
		{
			//trace("+++++++SKIP " + event.property);
		}
	}
	
	/**
	 * @private
	 */
	private function client_preinitializeHandler(event:FlexEvent):void
	{
		// this catches the component BEFORE createChildren() so we can set the
		// coreSkin if defined
		var client:IToolKitComponent = event.currentTarget as IToolKitComponent;
		client.removeEventListener(FlexEvent.PREINITIALIZE, client_preinitializeHandler);
		validateInitialization(client);
	}
	
	//--------------------------------------------------------------------------
	//
	//  Public Class :: Methods
	//
	//--------------------------------------------------------------------------
	
    /**
     * @private
     */
    public static function getInstance():IToolKitManager
    {
        if (!instance) 
        {
			instance = new ToolKitManagerImpl();
        }
        
        return instance;
    }
}
}